'HIDEasy
'Copyright (C) 2011 ASFX

'This program is free software; you can redistribute it and/or
'modify it under the terms of the GNU General Public License
'as published by the Free Software Foundation; either version 2
'of the License, or (at your option) any later version.

'This program is distributed in the hope that it will be useful,
'but WITHOUT ANY WARRANTY; without even the implied warranty of
'MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
'GNU General Public License for more details.

'You should have received a copy of the GNU General Public License
'along with this program; if not, write to the Free Software
'Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
Imports System.Runtime.InteropServices
Imports Microsoft.Win32.SafeHandles
Imports System.Text

<CLSCompliant(False)> _
Public Class HIDDevices
    Public Shared Function GetDeviceByPath(ByVal Path As String) As HIDDevice

        Dim _Devs As List(Of HIDDevice) = FindDevices()

        For Each _dev As HIDDevice In _Devs
            If _dev.Path=Path Then Return _dev
        Next

        Return Nothing

    End Function

    Public Shared Function GetDeviceByDescription(ByVal Description As String) As HIDDevice

        Dim _Devs As List(Of HIDDevice) = FindDevices()

        For Each _dev As HIDDevice In _Devs
            If _dev.Description=Description Then Return _dev
        Next

        Return Nothing

    End Function

    Public Shared Function FindDevices() As List(Of HIDDevice)

        Dim _handle As SafeFileHandle
        Dim _Devices As New List(Of HIDDevice)

        _handle = _
            NativeMethods.SetupDiGetClassDevs(HidGUID, vbNullString, New SafeFileHandle(IntPtr.Zero, True), _
                                              NativeMethods.FLAGS_DIGCF.DIGCF_PRESENT _
                                              Or NativeMethods.FLAGS_DIGCF.DIGCF_DEVICEINTERFACE)
        If _handle.IsInvalid Then
            Throw _
                New HIDException("Unable to find HID Devices.", _
                                 Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()))
        End If

        Dim _InfoData As NativeMethods.SP_DEVINFO_DATA
        With _InfoData
            .cbSize = Marshal.SizeOf(_InfoData)
            .DevInst = 0
            .ClassGuid = Guid.Empty
            .Reserved = IntPtr.Zero
        End With

        Dim _Index As Integer = 0

        While NativeMethods.SetupDiEnumDeviceInfo(_handle, _Index, _InfoData)=True

            Dim _InterfaceData As New NativeMethods.SP_DEVICE_INTERFACE_DATA
            Dim _InterfaceIndex As Integer = 0

            _Index += 1

            _InterfaceData.cbSize = Marshal.SizeOf(_InterfaceData)

            While _
                NativeMethods.SetupDiEnumDeviceInterfaces(_handle, _InfoData, HidGUID, _InterfaceIndex, _
                                                          _InterfaceData)=True
                Try
                    Dim _Path As String = GetDevicePath(_handle, _InterfaceData)
                    Dim _Description As String = GetDeviceDescription(_handle, _InfoData)
                    If Not _Path Is Nothing Then
                        Dim _HidDev As HIDDevice = Nothing
                        Try
                            _HidDev = New HIDDevice(_Path, _
                                                    _Description)
                        Catch ex As Exception
#If DEBUG Then
                            Debug.Print(String.Format("Unable to create a new HIDDevice '{0}' with path: {1}", _Description, _Path))
#End If
                        End Try
                        If _HidDev IsNot Nothing Then
                            _Devices.Add(_HidDev)
                        End If
                        _HidDev = Nothing
                    End If
                Catch ex2 As Exception
                Finally
                    _InterfaceIndex += 1
                End Try
            End While

        End While

        NativeMethods.SetupDiDestroyDeviceInfoList(_handle)

        Return _Devices

    End Function

    'Public Shared Function FindDevices() As Dictionary(Of String, String)

    '    Dim _handle As SafeFileHandle
    '    Dim _Devices As New Dictionary(Of String, String)

    '    _handle = _
    '        NativeMethods.SetupDiGetClassDevs(HidGUID, vbNullString, New SafeFileHandle(IntPtr.Zero, True), _
    '                                          NativeMethods.FLAGS_DIGCF.DIGCF_PRESENT _
    '                                          Or NativeMethods.FLAGS_DIGCF.DIGCF_DEVICEINTERFACE)
    '    If _handle.IsInvalid Then
    '        Throw _
    '            New HIDException("Unable to find HID Devices.", Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()))
    '    End If

    '    Dim _InfoData As NativeMethods.SP_DEVINFO_DATA
    '    With _InfoData
    '        .cbSize = Marshal.SizeOf(_InfoData)
    '        .DevInst = 0
    '        .ClassGuid = Guid.Empty
    '        .Reserved = IntPtr.Zero
    '    End With

    '    Dim _Index As Integer = 0

    '    While NativeMethods.SetupDiEnumDeviceInfo(_handle, _Index, _InfoData) = True

    '        Dim _InterfaceData As New NativeMethods.SP_DEVICE_INTERFACE_DATA
    '        Dim _InterfaceIndex As Integer = 0

    '        _Index += 1

    '        _InterfaceData.cbSize = Marshal.SizeOf(_InterfaceData)

    '        While _
    '            NativeMethods.SetupDiEnumDeviceInterfaces(_handle, _InfoData, HidGUID, _InterfaceIndex, _
    '                                                      _InterfaceData) = True
    '            Try
    '                Dim _Path As String = GetDevicePath(_handle, _InterfaceData)
    '                Dim _Description As String = GetDeviceDescription(_handle, _InfoData)
    '                If Not _Path Is Nothing Then
    '                    Dim _HidDev As HIDDevice = Nothing
    '                    Try
    '                        _HidDev = New HIDDevice(_Path, _
    '                                                _Description)
    '                    Catch ex As Exception
    '                        ' You can't connect with your default setting
    '                        '_Devices.Add(_Path, "[Unauthorized Device] " & _Description & "(" & ex.Message & ")")
    '                    End Try
    '                    If _HidDev IsNot Nothing Then
    '                        _Devices.Add(_HidDev.Path, _HidDev.Description)
    '                        _HidDev.Dispose()
    '                    End If
    '                    _HidDev = Nothing
    '                End If
    '            Catch ex2 As Exception
    '            Finally
    '                _InterfaceIndex += 1
    '            End Try
    '        End While

    '    End While

    '    NativeMethods.SetupDiDestroyDeviceInfoList(_handle)

    '    Return _Devices

    'End Function

    Private Shared ReadOnly Property HidGUID() As Guid
        Get

            Static _ClassGUID As Guid

            If _ClassGUID.Equals(Guid.Empty) Then NativeMethods.HidD_GetHidGuid(_ClassGUID)

            Return _ClassGUID

        End Get
    End Property

    Private Shared Function GetDevicePath(ByVal DeviceInfoSet As SafeFileHandle, _
                                          ByVal DeviceInterfaceData As NativeMethods.SP_DEVICE_INTERFACE_DATA) As String

        Dim _DetailDataBuffer As New SafeFileHandle(IntPtr.Zero, True)
        Dim _BufferSize As Integer
        Dim _DevicePath As String = ""
        Try
            NativeMethods.SetupDiGetDeviceInterfaceDetail(DeviceInfoSet, DeviceInterfaceData, _
                                                          New SafeFileHandle(IntPtr.Zero, True), 0, _BufferSize, _
                                                          New SafeFileHandle(IntPtr.Zero, True))

            _DetailDataBuffer = New SafeFileHandle(Marshal.AllocHGlobal(_BufferSize), True)

            Select Case IntPtr.Size
                Case 4
                    Marshal.WriteInt32(_DetailDataBuffer.DangerousGetHandle, 4+Marshal.SystemDefaultCharSize)
                Case 8
                    Marshal.WriteInt32(_DetailDataBuffer.DangerousGetHandle, 8)
                Case Else
                    Throw New HIDException("Unable to get HID Device Path (intptr.size is not 4 or 8).")
            End Select

            If _
                NativeMethods.SetupDiGetDeviceInterfaceDetail(DeviceInfoSet, DeviceInterfaceData, _DetailDataBuffer, _
                                                              _BufferSize, Nothing, _
                                                              New SafeFileHandle(IntPtr.Zero, True))=True Then

                Dim DevicePathPointer As IntPtr = New IntPtr(_DetailDataBuffer.DangerousGetHandle.ToInt64+4)

                _DevicePath = Marshal.PtrToStringAuto(DevicePathPointer)
            Else
                Throw _
                    New HIDException("Unable to get HID Device Path.", _
                                     Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()))
            End If
        Catch ex As Exception
            Throw New HIDException("Unable to 'GetDevicePath'.", ex)
        Finally
            Marshal.FreeHGlobal(_DetailDataBuffer.DangerousGetHandle)
            '_DetailDataBuffer.Close()
            _DetailDataBuffer.Dispose()
            _DetailDataBuffer = Nothing
        End Try

        Return _DevicePath

    End Function

    Private Shared Function GetDeviceDescription(ByVal DeviceInfoSet As SafeFileHandle, _
                                                 ByVal DeviceInfosData As NativeMethods.SP_DEVINFO_DATA) As String
        Dim _RequiredSize As Integer = 1024
        Dim _RegType As New SafeFileHandle(IntPtr.Zero, True)
        Dim _Buffer() As Byte
        'IntPtr = Marshal.AllocHGlobal(_RequiredSize - 1)
        Dim _Description As String

        Try
            ReDim _Buffer(_RequiredSize-1)

            If _
                (NativeMethods.SetupDiGetDeviceRegistryProperty(DeviceInfoSet, DeviceInfosData, _
                                                                NativeMethods.FLAGS_SPDRP.SPDRP_DEVICEDESC, _RegType, _
                                                                _Buffer, _RequiredSize, _RequiredSize)) Then
                '_Description = Marshal.PtrToStringBSTR(_Buffer, _RequiredSize)
                _Description = ASCIIEncoding.UTF7.GetString(_Buffer, 0, _RequiredSize-1)
            Else
                _Description = String.Empty
                Throw _
                    New HIDException("Unable to get Device Description.", _
                                     Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()))
            End If
        Catch ex As Exception
            Throw New HIDException("Unable to get Device Description.", ex)
        Finally
            Marshal.FreeHGlobal(_RegType.DangerousGetHandle)
            _RegType.Close()
            _RegType.Dispose()
            _RegType = Nothing
        End Try
        Return _Description
    End Function
End Class
